#!/usr/bin/env python

import helper

from helper import get_prop, gen_id, cross_prod

TYPE_PLAYER = 'r_player'
TYPE_MOVEMENT = 'r_movement'
TYPE_PLACEMENT = 'r_placement'
TYPE_VISION = 'r_vision'
TYPE_ATTACK = 'r_attack'
TYPE_HP = 'r_hp'
TYPE_FUEL = 'r_fuel'

PROP_ALL = 'all'
PROP_SOME = 'some'

class RuleError(Exception):
  def __init__(self, value):
    self.value = value
  
  def __str__(self):
    return repr(self.value)

class Rule:
  def __init__(self, rule_type, *args, **properties):
    self.rule_type = rule_type
    self.properties = { }
    if rule_type == TYPE_MOVEMENT or rule_type == TYPE_PLACEMENT:
      self.set_optional_properties(properties, 'unit_name', 'unit_class')
      self.set_optional_properties(properties, 'tile_name', 'terrain_type')
      
    if rule_type == TYPE_MOVEMENT:
      self.set_property(properties, 'cost', must_exist=True)
    
    if rule_type == TYPE_PLACEMENT:
      self.set_property(properties, 'can_place', must_exist=True)
    
    if rule_type == TYPE_HP or rule_type == TYPE_FUEL:
      self.set_property(properties, 'unit_class', must_exist=True)
      self.set_property(properties, 'tile_name', must_exist=True)
      self.set_property(properties, 'replenish', must_exist=True)
      
  
  def get_property(self, property):
    if not self.has_property(property):
      return None
    else:
      return self.properties[property]
  
  def has_property(self, property):
    return property in self.properties
  
  def set_optional_properties(self, properties, *opts):
    for opt in opts:
      self.set_property(properties, opt)
      if self.get_property(opt) is not None:
        return
    raise RuleError('One of these properties must exist: %s.' % (' '.join(opts)))
  
  def set_property(self, properties, property, *args, **p):
    self.properties[property] = get_prop(properties, property, **p)
  
  def get_type(self):
    return self.rule_type
  

class RuleEngine:
  def __init__(self):
    self.rules = { }
  
  def register_rule(self, rule):
    r_type = rule.get_type()
    
    if r_type not in self.rules:
      self.rules[r_type] = { }
    id = gen_id(0, 2000 - 1, self.rules[r_type].keys())
    self.rules[r_type][id] = rule
    #self.rules[r_type] = rule
  
  def rule_matches(self, rule, *args, **props):
    match_type = get_prop(props, 'match_type', def_val=PROP_ALL)
    
    for prop, value in args:
      if rule.get_property(prop) == value:
        if match_type == PROP_SOME:
          return True
      else:
        if match_type == PROP_ALL:
          return False
    return match_type == PROP_ALL
  
  def get_rules_by_type(self, rule_type):
    return self.rules[rule_type].values()
  
  
  def get_movement_rule(self, unit_type, tile_type):
    m_rules = self.get_rules_by_type(TYPE_MOVEMENT)
    unit_criteria = [ ('unit_name', unit_type.get_name()), 
                      ('unit_class', unit_type.get_unit_class())]
    tile_criteria = [ ('tile_name', tile_type.get_name()),
                      ('terrain_type', tile_type.get_terrain_type())]
    for m_rule in m_rules:
      for uu, tt in cross_prod(unit_criteria, tile_criteria):
        if self.rule_matches(m_rule, (uu[0], uu[1]), (tt[0], tt[1])):
          return m_rule
    return None
    
  def get_placement_rule(self, unit_type, tile_type):
    p_rules = self.get_rules_by_type(TYPE_PLACEMENT)
    unit_criteria = [ ('unit_name', unit_type.get_name()), 
                      ('unit_class', unit_type.get_unit_class())]
    tile_criteria = [ ('tile_name', tile_type.get_name()),
                      ('terrain_type', tile_type.get_terrain_type())]
    for p_rule in p_rules:
      for uu, tt in cross_prod(unit_criteria, tile_criteria):
        if self.rule_matches(p_rule, (uu[0], uu[1]), (tt[0], tt[1])):
          return p_rule
    return None
  
  def get_hp_rule(self, unit_type, tile_type):
    hp_rules = self.get_rules_by_type(TYPE_HP)
    for hp_rule in hp_rules:
      if self.rule_matches(hp_rule, ('unit_class', unit_type.get_unit_class()), 
                                    ('tile_type', tile_type.get_name())):
        return hp_rule
    return None
  
  def get_fuel_rule(self, unit_type, tile_type):
    f_rules = self.get_rules_by_type(TYPE_FUEL)
    for f_rule in f_rules:
      if self.rule_matches(f_rule, ('unit_class', unit_type.get_unit_class()), 
                                    ('tile_type', tile_type.get_name())):
        return f_rule
    return None
  
